home *** CD-ROM | disk | FTP | other *** search
/ InterCD 2001 November / november_2001.iso / Browsers / Netscape 6.1 / browser.xpi / bin / chrome / comm.jar / content / editor / EdLinkProps.js < prev    next >
Encoding:
JavaScript  |  2001-06-28  |  16.0 KB  |  531 lines

  1. /* 
  2.  * The contents of this file are subject to the Netscape Public
  3.  * License Version 1.1 (the "License"); you may not use this file
  4.  * except in compliance with the License. You may obtain a copy of
  5.  * the License at http://www.mozilla.org/NPL/
  6.  *  
  7.  * Software distributed under the License is distributed on an "AS
  8.  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  9.  * implied. See the License for the specific language governing
  10.  * rights and limitations under the License.
  11.  *  
  12.  * The Original Code is Mozilla Communicator client code, released
  13.  * March 31, 1998.
  14.  * 
  15.  * The Initial Developer of the Original Code is Netscape
  16.  * Communications Corporation. Portions created by Netscape are
  17.  * Copyright (C) 1998-1999 Netscape Communications Corporation. All
  18.  * Rights Reserved.
  19.  * 
  20.  * Contributor(s): 
  21.  */
  22.  
  23. var anchorElement = null;
  24. var imageElement = null;
  25. var insertNew = false;
  26. var replaceExistingLink = false;
  27. var insertLinkAtCaret;
  28. var needLinkText = false;
  29. var href;
  30. var newLinkText;
  31. var HNodeArray;
  32. var gHaveNamedAnchors = false;
  33. var gHaveHeadings = false;
  34. var gCanChangeHeadingSelected = true;
  35. var gCanChangeAnchorSelected = true;
  36. var dialog;
  37.  
  38. // NOTE: Use "href" instead of "a" to distinguish from Named Anchor
  39. // The returned node is has an "a" tagName
  40. var tagName = "href";
  41.  
  42. // dialog initialization code
  43. function Startup()
  44. {
  45.   if (!InitEditorShell())
  46.     return;
  47.  
  48.   doSetOKCancel(onOK, onCancel);
  49.  
  50.   dialog = new Object;
  51.   if (!dialog)
  52.   {
  53.     dump("Failed to create dialog object!!!\n");
  54.     window.close();
  55.     return;
  56.   }
  57.  
  58.   // Message was wrapped in a <label> or <div>, so actual text is a child text node
  59.   dialog.linkTextCaption     = document.getElementById("linkTextCaption");
  60.   dialog.linkTextMessage     = document.getElementById("linkTextMessage");
  61.   dialog.linkTextInput       = document.getElementById("linkTextInput");
  62.   dialog.hrefInput           = document.getElementById("hrefInput");
  63.   dialog.NamedAnchorList     = document.getElementById("NamedAnchorList");
  64.   dialog.HeadingsList        = document.getElementById("HeadingsList");
  65.   dialog.MoreSection         = document.getElementById("MoreSection");
  66.   dialog.MoreFewerButton     = document.getElementById("MoreFewerButton");
  67.   dialog.AdvancedEditSection = document.getElementById("AdvancedEdit");
  68.  
  69.   var selection = editorShell.editorSelection;
  70.   if (selection)
  71.     dump("There is a selection: collapsed = "+selection.isCollapsed+"\n");
  72.   else
  73.     dump("Failed to get selection\n");
  74.  
  75.   // See if we have a single selected image
  76.   imageElement = editorShell.GetSelectedElement("img");
  77.  
  78.   if (imageElement)
  79.   {
  80.     // Get the parent link if it exists -- more efficient than GetSelectedElement()
  81.     anchorElement = editorShell.GetElementOrParentByTagName("href", imageElement);
  82.     if (anchorElement)
  83.     {
  84.       if (anchorElement.childNodes.length > 1)
  85.       {
  86.         // If there are other children, then we want to break
  87.         //  this image away by inserting a new link around it,
  88.         //  so make a new node and copy existing attributes
  89.         anchorElement = anchorElement.cloneNode(false);
  90.         //insertNew = true;
  91.         replaceExistingLink = true;
  92.       }
  93.     }
  94.   }
  95.   else
  96.   {
  97.     // Get an anchor element if caret or
  98.     //   entire selection is within the link.
  99.     anchorElement = editorShell.GetSelectedElement(tagName);
  100.  
  101.     if (anchorElement)
  102.     {
  103.       // Select the entire link
  104.       editorShell.SelectElement(anchorElement);
  105.       selection = editorShell.editorSelection;
  106.     }
  107.     else
  108.     {
  109.       // If selection starts in a link, but extends beyond it,
  110.       //   the user probably wants to extend existing link to new selection,
  111.       //   so check if either end of selection is within a link
  112.       // POTENTIAL PROBLEM: This prevents user from selecting text in an existing
  113.       //   link and making 2 links. 
  114.       // Note that this isn't a problem with images, handled above
  115.  
  116.       anchorElement = editorShell.GetElementOrParentByTagName("href", selection.anchorNode);
  117.       if (!anchorElement)
  118.         anchorElement = editorShell.GetElementOrParentByTagName("href", selection.focusNode);
  119.  
  120.       if (anchorElement)
  121.       {
  122.         // But clone it for reinserting/merging around existing
  123.         //   link that only partially overlaps the selection
  124.         anchorElement = anchorElement.cloneNode(false);
  125.         //insertNew = true;
  126.         replaceExistingLink = true;
  127.       }
  128.     }
  129.   }
  130.  
  131.   if(!anchorElement)
  132.   {
  133.     // No existing link -- create a new one
  134.     anchorElement = editorShell.CreateElementWithDefaults(tagName);
  135.     insertNew = true;
  136.     // Hide message about removing existing link
  137.     document.getElementById("RemoveLinkMsg").setAttribute("hidden","true");
  138.   }
  139.   if(!anchorElement)
  140.   {
  141.     dump("Failed to get selected element or create a new one!\n");
  142.     window.close();
  143.     return;
  144.   } 
  145.  
  146.   // We insert at caret only when nothing is selected
  147.   insertLinkAtCaret = selection.isCollapsed;
  148.   
  149.   var selectedText;
  150.   if (insertLinkAtCaret)
  151.   {
  152.     // Titledbox caption:
  153.     dialog.linkTextCaption.setAttribute("value",GetString("LinkText"));
  154.     // Message above input field:
  155.     dialog.linkTextMessage.setAttribute("value", GetString("EnterLinkText"));
  156.   }
  157.   else
  158.   {
  159.     if (!imageElement)
  160.     {
  161.       // We get here if selection is exactly around a link node
  162.       // Check if selection has some text - use that first
  163.       selectedText = GetSelectionAsText();
  164.       if (selectedText) 
  165.       {
  166.         // No text, look for first image in the selection
  167.         var children = anchorElement.childNodes;
  168.         if (children)
  169.         {
  170.           for(var i=0; i < children.length; i++) 
  171.           {
  172.             var nodeName = children.item(i).nodeName.toLowerCase();
  173.             if (nodeName == "img")
  174.             {
  175.               imageElement = children.item(i);
  176.               break;
  177.             }
  178.           }
  179.         }
  180.       }
  181.     }
  182.     // Set "caption" for link source and the source text or image URL
  183.     if (imageElement)
  184.     {
  185.       dialog.linkTextCaption.setAttribute("value",GetString("LinkImage"));
  186.       // Link source string is the source URL of image
  187.       // TODO: THIS DOESN'T HANDLE MULTIPLE SELECTED IMAGES!
  188.       dialog.linkTextMessage.setAttribute("value",imageElement.src);
  189.     } else {
  190.       dialog.linkTextCaption.setAttribute("value",GetString("LinkText"));
  191.       if (selectedText) 
  192.       {
  193.         // Use just the first 40 characters and add "..."
  194.         dialog.linkTextMessage.setAttribute("value",TruncateStringAtWordEnd(selectedText, 40, true));
  195.       } else {
  196.         dialog.linkTextMessage.setAttribute("value",GetString("MixedSelection"));
  197.       }
  198.     }
  199.   }
  200.  
  201.   // Make a copy to use for AdvancedEdit and onSaveDefault
  202.   globalElement = anchorElement.cloneNode(false);
  203.  
  204.   // Get the list of existing named anchors and headings
  205.   FillListboxes();
  206.  
  207.   // Set data for the dialog controls
  208.   InitDialog();
  209.   
  210.   // Search for a URI pattern in the selected text
  211.   //  as candidate href
  212.   selectedText = TrimString(selectedText); 
  213.   if (!dialog.hrefInput.value && TextIsURI(selectedText))
  214.       dialog.hrefInput.value = selectedText;
  215.  
  216.   // Set initial focus
  217.   if (insertLinkAtCaret) {
  218.     // We will be using the HREF inputbox, so text message
  219.     SetTextboxFocus(dialog.linkTextInput);
  220.   } else {
  221.     SetTextboxFocus(dialog.hrefInput);
  222.  
  223.     // We will not insert a new link at caret, so remove link text input field
  224.     dialog.linkTextInput.setAttribute("hidden","true");
  225.     dialog.linkTextInput = null;
  226.   }
  227.  
  228.   InitMoreFewer();
  229.     
  230.   // This sets enable state on OK button
  231.   ChangeText();
  232.  
  233.   SetWindowLocation();
  234. }
  235.  
  236. // Set dialog widgets with attribute data
  237. // We get them from globalElement copy so this can be used
  238. //   by AdvancedEdit(), which is shared by all property dialogs
  239. function InitDialog()
  240. {
  241.   // Must use getAttribute, not "globalElement.href", 
  242.   //  or foreign chars aren't coverted correctly!
  243.   dialog.hrefInput.value = globalElement.getAttribute("href");
  244. }
  245.  
  246. function chooseFile()
  247. {
  248.   // Get a local file, converted into URL format
  249.   var fileName = GetLocalFileURL("html");
  250.   if (fileName) {
  251.     dialog.hrefInput.value = fileName;
  252.     // Call this to do OK button enabling
  253.     ChangeText();
  254.   }
  255.   // Put focus into the input field
  256.   SetTextboxFocus(dialog.hrefInput);
  257. }
  258.  
  259. function FillListboxes()
  260. {
  261.   var NamedAnchorNodeList = editorShell.editorDocument.anchors;
  262.   var NamedAnchorCount = NamedAnchorNodeList.length;
  263.   var item;
  264.   if (NamedAnchorCount > 0)
  265.   {
  266.     for (var i = 0; i < NamedAnchorCount; i++)
  267.       AppendStringToTreelist(dialog.NamedAnchorList, NamedAnchorNodeList.item(i).name);
  268.  
  269.     gHaveNamedAnchors = true;
  270.   } 
  271.   else 
  272.   {
  273.     // Message to tell user there are none
  274.     item = AppendStringToTreelistById(dialog.NamedAnchorList, "NoNamedAnchors");
  275.     if (item) item.setAttribute("disabled", "true");
  276.   }
  277.   var firstHeading = true;
  278.   for (var j = 1; j <= 6; j++)
  279.   {
  280.     var headingList = editorShell.editorDocument.getElementsByTagName("h"+String(j));
  281.     if (headingList.length > 0)
  282.     {
  283.       var heading = headingList.item(0);
  284.  
  285.       // Skip headings that already have a named anchor as their first child
  286.       //  (this may miss nearby anchors, but at least we don't insert another
  287.       //   under the same heading)
  288.       var child = heading.firstChild;
  289.       if (child && child.nodeName == "A" && child.name && (child.name.length>0))
  290.         continue;
  291.  
  292.       var range = editorShell.editorDocument.createRange();
  293.       range.setStart(heading,0);
  294.       var lastChildIndex = heading.childNodes.length;
  295.       range.setEnd(heading,lastChildIndex);
  296.       var text = range.toString();
  297.       if (text)
  298.       {
  299.         // Use just first 40 characters, don't add "...",
  300.         //  and replace whitespace with "_" and strip non-word characters
  301.         text = ConvertToCDATAString(TruncateStringAtWordEnd(text, 40, false));
  302.         // Append "_" to any name already in the list
  303.         if (GetExistingHeadingIndex(text) > -1)
  304.           text += "_";
  305.         AppendStringToTreelist(dialog.HeadingsList, text);
  306.  
  307.         // Save nodes in an array so we can create anchor node under it later
  308.         if (!HNodeArray)
  309.           HNodeArray = new Array(heading)
  310.         else
  311.           HNodeArray[HNodeArray.length] = heading;
  312.       }
  313.     }
  314.   }
  315.   if (HNodeArray)
  316.   {
  317.     gHaveHeadings = true;
  318.   } else {
  319.     // Message to tell user there are none
  320.     item = AppendStringToTreelistById(dialog.HeadingsList, "NoHeadings");
  321.     if (item) item.setAttribute("disabled", "true");
  322.   }
  323. }
  324.  
  325. function ChangeText()
  326. {
  327.   var enable = true;
  328.  
  329.   // Disable OK button only if inserting a new link
  330.   // (allow empty location to remove existing link)
  331.   if (insertNew)
  332.   {
  333.     if (insertLinkAtCaret)
  334.       enable = dialog.linkTextInput.value.trimString().length > 0;
  335.  
  336.     if (enable)
  337.       enable = dialog.hrefInput.value.trimString().length > 0;
  338.   }
  339.  
  340.   SetElementEnabledById( "ok", enable);
  341. }
  342.  
  343. var gClearListSelections = true;
  344.  
  345. function ChangeLocation()
  346. {
  347.   if (gClearListSelections)
  348.   {
  349.     // Unselect the treelists
  350.     UnselectNamedAnchor();
  351.     UnselectHeadings();
  352.   }  
  353.   // Set OK button enable state
  354.   ChangeText();
  355. }
  356.  
  357. function GetExistingHeadingIndex(text)
  358. {
  359.   var len = dialog.HeadingsList.getAttribute("length");
  360.   for (var i=0; i < len; i++)
  361.   {
  362.     if (GetTreelistValueAt(dialog.HeadingsList, i) == text)
  363.       return i;
  364.   }
  365.   return -1;
  366. }
  367.  
  368. function SelectNamedAnchor()
  369. {
  370.   if (gCanChangeAnchorSelected)
  371.   {
  372.     if (gHaveNamedAnchors)
  373.     {
  374.       // Prevent ChangeLocation() from unselecting the list
  375.       gClearListSelections = false;
  376.       dialog.hrefInput.value = "#"+GetSelectedTreelistValue(dialog.NamedAnchorList);
  377.       gClearListSelections = true;
  378.  
  379.       // ChangeLocation isn't always called, so be sure Ok is enabled
  380.       ChangeText();
  381.     }
  382.     else
  383.       UnselectNamedAnchor();
  384.   
  385.     UnselectHeadings();
  386.   }
  387. }
  388.  
  389. function SelectHeading()
  390. {
  391.   if (gCanChangeHeadingSelected)
  392.   {
  393.     if (gHaveHeadings)
  394.     {
  395.       gClearListSelections = false;
  396.       dialog.hrefInput.value = "#"+GetSelectedTreelistValue(dialog.HeadingsList);
  397.       gClearListSelections = true;
  398.  
  399.       ChangeText();
  400.     }
  401.     else
  402.       UnselectHeadings();
  403.  
  404.     UnselectNamedAnchor();
  405.   }
  406. }
  407.  
  408. function UnselectNamedAnchor()
  409. {
  410.   // Prevent recursive calling of SelectNamedAnchor()
  411.   gCanChangeAnchorSelected = false;
  412.   dialog.NamedAnchorList.selectedIndex = -1;  
  413.   gCanChangeAnchorSelected = true;
  414. }
  415.  
  416. function UnselectHeadings()
  417. {
  418.   // Prevent recursive calling of SelectHeading()
  419.   gCanChangeHeadingSelected = false;
  420.   dialog.HeadingsList.selectedIndex = -1;  
  421.   gCanChangeHeadingSelected = true;
  422. }
  423.  
  424. // Get and validate data from widgets.
  425. // Set attributes on globalElement so they can be accessed by AdvancedEdit()
  426. function ValidateData()
  427. {
  428.   href = dialog.hrefInput.value.trimString();
  429.   if (href.length > 0)
  430.   {
  431.     // Set the HREF directly on the editor document's anchor node
  432.     //  or on the newly-created node if insertNew is true
  433.     globalElement.setAttribute("href",href);
  434.   }
  435.   else if (insertNew)
  436.   {
  437.     // We must have a URL to insert a new link
  438.     //NOTE: We accept an empty HREF on existing link to indicate removing the link
  439.     ShowInputErrorMessage(GetString("EmptyHREFError"));
  440.     return false;
  441.   }
  442.   if (dialog.linkTextInput)
  443.   {
  444.     // The text we will insert isn't really an attribute,
  445.     //  but it makes sense to validate it
  446.     newLinkText = TrimString(dialog.linkTextInput.value);
  447.     if (newLinkText.length == 0)
  448.     {
  449.       ShowInputErrorMessage(GetString("EmptyLinkTextError"));
  450.       SetTextboxFocus(dialog.linkTextInput);
  451.       return false;
  452.     }
  453.   }
  454.   return true;
  455. }
  456.  
  457. function doHelpButton()
  458. {
  459.   openHelp("chrome://help/content/help.xul?link_properties");
  460. }
  461.  
  462. function onOK()
  463. {
  464.   if (ValidateData())
  465.   {
  466.     if (href.length > 0)
  467.     {
  468.       // Copy attributes to element we are changing or inserting
  469.       editorShell.CloneAttributes(anchorElement, globalElement);
  470.  
  471.       // Coalesce into one undo transaction
  472.       editorShell.BeginBatchChanges();
  473.  
  474.       // Get text to use for a new link
  475.       if (insertLinkAtCaret)
  476.       {
  477.         // Append the link text as the last child node 
  478.         //   of the anchor node
  479.         var textNode = editorShell.editorDocument.createTextNode(newLinkText);
  480.         if (textNode)
  481.           anchorElement.appendChild(textNode);
  482.         try {
  483.           editorShell.InsertElementAtSelection(anchorElement, false);
  484.         } catch (e) {
  485.           dump("Exception occured in InsertElementAtSelection\n");
  486.           return true;
  487.         }
  488.       } else if (insertNew || replaceExistingLink)
  489.       {
  490.         //  Link source was supplied by the selection,
  491.         //  so insert a link node as parent of this
  492.         //  (may be text, image, or other inline content)
  493.         try {
  494.           editorShell.InsertLinkAroundSelection(anchorElement);
  495.         } catch (e) {
  496.           dump("Exception occured in InsertElementAtSelection\n");
  497.           return true;
  498.         }
  499.       }
  500.       // Check if the link was to a heading 
  501.       if (href[0] == "#")
  502.       {
  503.         var name = href.substr(1);
  504.         var index = GetExistingHeadingIndex(name);
  505.         if (index >= 0) {
  506.           // We need to create a named anchor 
  507.           //  and insert it as the first child of the heading element
  508.           var headNode = HNodeArray[index];
  509.           var anchorNode = editorShell.editorDocument.createElement("a");
  510.           if (anchorNode) {
  511.             anchorNode.name = name;
  512.             // Remember to use editorShell method so it is undoable!
  513.             editorShell.InsertElement(anchorNode, headNode, 0, false);
  514.           }
  515.         } else {
  516.           dump("HREF is a heading but is not in the list!\n");
  517.         }
  518.       }
  519.       editorShell.EndBatchChanges();
  520.     } 
  521.     else if (!insertNew)
  522.     {
  523.       // We already had a link, but empty HREF means remove it
  524.       editorShell.RemoveTextProperty("a", "");
  525.     }
  526.     SaveWindowLocation();
  527.     return true;
  528.   }
  529.   return false;
  530. }
  531.